import requests
import allure
import urllib3
from typing import Union

from pyassured.core.content_type import ContentType
from pyassured.core.global_data import global_data
from pyassured.core.response_options import ValidatableResponse, ExtractableResponse
from pyassured.core.logger import Log
from pyassured.core.read_yaml import yaml_config
from pyassured.core.function_helper import FunctionHelper
from pyassured.core.emoji_utils import EmojiUtils
from pyassured.core.log_helper import LogHelper

urllib3.disable_warnings()
log = Log()


class BaseApi:
    """基础API请求类"""

    path = None
    method = None
    headers = None
    params = None
    json = None
    data = None
    files = None
    timeout = 30
    verify = False
    base_uri = None
    port = None

    def __init__(self):

        self.__response = None
        try:
            self.application_config = yaml_config.read_application_config("application.yaml")
        except FileNotFoundError as e:
            raise FileNotFoundError(
                f"配置文件加载失败: {e}\n\n"
                f"解决方案:\n"
                f"1. 确保在项目根目录下存在 application.yaml 配置文件\n"
                f"2. 或者使用 'pax create-project' 命令创建新项目\n"
            )
        except ValueError as e:
            raise ValueError(
                f"配置文件格式错误: {e}\n\n"
            )

        # 设置基础URI
        if self.base_uri is None:
            self.base_uri = self.application_config.get('host', '')

        # 构建完整URL
        self.url = f"{self.base_uri}{self.path}" if self.path else self.base_uri

    def run(self, res=None, desc=None) -> 'BaseApi':
        """执行API请求

        Args:
            res: 上一步的响应结果，用于数据传递
            desc: 请求描述，用于Allure报告

        Returns:
            self: 返回自身实例，支持链式调用
        """
        # 转换和替换请求数据中的全局变量
        if res is not None:
            if hasattr(res, 'VStep'):
                self.__covert_data(res.VStep)
            else:
                self.__covert_data(res)

        # 设置默认描述
        if desc is None:
            desc = f"发送请求: {self.method} {self.path}"

        # 记录测试步骤
        log.test_step(f"开始执行API请求: {desc}")

        with requests.session() as session:
            # 记录请求日志
            start_time = LogHelper.log_request(
                url=self.url,
                method=self.method,
                params=self.params,
                headers=self.headers,
                data=self.data,
                json_data=self.json
            )

            try:
                # 发送请求
                self.__response = session.request(
                    url=self.url,
                    method=self.method,
                    params=self.params,
                    headers=self.headers,
                    data=self.data,
                    json=self.json,
                    files=self.files,
                    timeout=self.timeout,
                    verify=self.verify
                )

                # 记录Allure报告
                self.__print_report(desc)

                # 记录响应日志
                LogHelper.log_response(response=self.__response, start_time=start_time)

                # 记录请求成功
                log.success(f"请求成功完成: {self.method} {self.path} → {self.__response.status_code}")

                return self
            except requests.RequestException as e:
                log.error(f"{EmojiUtils.ERROR} 请求失败: {e}")
                log.error(
                    f"{EmojiUtils.URL} URL: {self.url}\n{EmojiUtils.get_http_method_emoji(self.method)} 方法: {self.method}")
                raise
            except Exception as e:
                log.error(f"{EmojiUtils.ERROR} 请求过程中发生意外错误: {e}")
                raise

    def assert_that(self):
        """获取响应验证器

        Returns:
            ValidatableResponse: 用于断言的响应对象
        """
        if self.__response is None:
            raise ValueError("没有可用的响应数据。您是否先调用了 run() 方法？")
        return ValidatableResponse(self.__response)

    def extract(self):
        """获取响应提取器

        Returns:
            ExtractableResponse: 用于提取数据的响应对象
        """
        if self.__response is None:
            raise ValueError("没有可用的响应数据。您是否先调用了 run() 方法？")
        return ExtractableResponse(self.__response)

    def __covert_data(self, res=None):
        """转换请求数据，替换全局变量

        Args:
            res: 上一步的响应数据
        """
        # 合并全局头信息
        global_headers = self.application_config.get('headers', {})
        if not self.headers:
            self.headers = global_headers.copy()
        else:
            for key, value in global_headers.items():
                if key not in self.headers:
                    self.headers[key] = value

        # 替换全局变量
        self.url = global_data.resolve_global_var(self.url, res)
        self.path = global_data.resolve_global_var(self.path, res)

        # 替换其他请求数据中的全局变量
        self.__replace_global_data(self.headers, res)
        self.__replace_global_data(self.params, res)
        self.__replace_global_data(self.json, res)
        self.__replace_global_data(self.data, res)

    def __replace_global_data(self, src_data, res=None, debugMode=None):
        """替换数据中的全局变量

        Args:
            src_data: 源数据
            res: 上一步的响应数据
            debugMode: 调试模式
        """
        if debugMode is None:
            debugMode = self.application_config.get('debugMode', False)

        if src_data is not None and isinstance(src_data, dict):
            for key, value in list(src_data.items()):
                if isinstance(value, str):
                    src_data[key] = FunctionHelper.parse_function_str(
                        global_data.resolve_global_var(value, res, debugMode)
                    )
                # 递归处理嵌套字典
                elif isinstance(value, dict):
                    self.__replace_global_data(value, res, debugMode)
                # 处理列表中的每个元素
                elif isinstance(value, list):
                    for i, item in enumerate(value):
                        if isinstance(item, dict):
                            self.__replace_global_data(item, res, debugMode)
                        elif isinstance(item, str):
                            value[i] = FunctionHelper.parse_function_str(
                                global_data.resolve_global_var(item, res, debugMode)
                            )

    # 链式方法设置请求参数
    def set_url(self, url):
        """设置请求URL"""
        self.url = url
        return self

    def set_method(self, method):
        """设置请求方法"""
        self.method = method
        return self

    def set_content_type(self, content_type: Union[ContentType, str]):
        """设置Content-Type

        Args:
            content_type: 内容类型，可以是ContentType枚举或字符串
        """
        self.headers['Content-Type'] = content_type
        return self

    def with_params(self, params):
        """设置查询参数"""
        self.params = params
        return self

    def with_headers(self, headers):
        """设置或更新请求头"""
        if not self.headers:
            self.headers = {}
        self.headers.update(headers)
        return self

    def with_cookies(self, cookies):
        """设置cookies"""
        self.cookies = cookies
        return self

    def with_data(self, data):
        """设置表单数据"""
        self.data = data
        return self

    def with_json(self, json_data):
        """设置JSON数据"""
        self.json = json_data
        return self

    def with_path_params(self, params):
        """替换URL中的路径参数

        Args:
            params: 路径参数字典，如 {"id": 123}
        """
        if not params:
            return self

        for key, value in params.items():
            placeholder = "{" + key + "}"
            self.url = self.url.replace(placeholder, str(value))
        return self

    def set_verify(self, verify):
        """设置SSL验证"""
        self.verify = verify
        return self

    def get_response(self):
        """获取原始响应对象"""
        return self.__response

    @allure.step('{description}')
    def __print_report(self, description=None):
        """将请求和响应信息添加到Allure报告"""
        LogHelper.add_allure_request_attachments(
            url=self.url,
            method=self.method,
            params=self.params,
            headers=self.headers,
            data=self.data,
            json_data=self.json
        )

        if self.__response:
            LogHelper.add_allure_response_attachments(self.__response)


class TestStep:
    """测试步骤类，封装API响应处理"""

    def __init__(self, run):
        self.__run = run

    def response(self) -> requests.Response:
        return self.__run.response()

    @property
    def VStep(self):
        """获取上一步提取的值

        Returns:
            Dict: 提取的变量字典
        """
        if isinstance(self.__run, ExtractableResponse):
            values = self.__run.step_values()
            if values:
                log.info(f"{EmojiUtils.DATA_EXTRACT} 获取提取的变量: {list(values.keys())}")
            return values
        return {}


class Desc:
    """用于为API请求添加描述"""

    def __init__(self, description):
        self.description = description

    def __call__(self, cls):
        original_run = cls.run

        def new_run(self_obj, res=None):
            log.test_step(f"{EmojiUtils.TEST_STEP} {self.description}")
            result = original_run(self_obj, res, self.description)

            return result

        cls.run = new_run

        return cls
